# @Time : 2019/12/12 17:55 
# @Author : Fioman 
# @Phone : 13149920693
"""
socket通信类,主要负责和服务器端的通信问题
"""
import queue
import socket
import threading
import gevent
import win32api

from tools.my_logger import *
from tools.db_handler import DB_handler
import settings
import json
from hd_socket.parse_xml import *


class Hd_Client(object):

  def __init__(self, server_ip, port, logger, db, msg_q):
    """
    :param server_ip: 服务器ip地址
    :param port: 连接端口
    :param logger: 日志文件
    :param db: 数据库对象
    """
    self.logger = logger
    self.db = db
    self.addr = (server_ip, port)
    self.msg_q = msg_q
    self.error_msg = None
    self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.modify_buff_size(self.sock)  # 更改发送和接收的缓存区的大小
    try:
      data = self.db._get_data(self.db.params_db, "device_info")[1][0]
      self.ask_duration = int(data.get('ask_duration'))
      self.datetime_duration = int(data.get('datetime_duration'))
      self.operation_mode = data.get('operation_mode')
      self.total_count_duration = data.get('total_count_duration')
      self.equipment_status = data.get('equipment_status')
    except Exception as e:
      self.logger.debug('数据库操作异常..{}'.format(str(e)))
      self.ask_duration = 60
      self.datetime_duration = 60
      self.operation_mode = "1"
      self.total_count_duration = 2
    try:
      self.sock.connect(self.addr)
    except Exception as e:
      print('连接失败')
      self.logger.debug('和EC系统连接失败,请检查网络是否接通..', str(e))
      self.error_msg = '和EC系统连接失败,请检查网络是否接通..'
    self.recv_thread = threading.Thread(target=self.recv_msg, name='recv_thread')
    self.send_thread = threading.Thread(target=self.send_msg, name='send_thread')
    self.manager_thread = threading.Thread(target=self.manager, name='manager_thread')

  def get_mode_and_duration(self):
    try:
      data = self.db._get_data(self.db.params_db, "device_info")[1][0]
      ask_duration = data.get('ask_duration')
      datetime_duration = data.get('datetime_duration')
      operation_mode = data.get('operation_mode')
      operation_list = ["手动模式", "自动模式", "半自动模式"]
      operation_mode_ret = operation_list[int(operation_mode)]
    except Exception as e:
      self.logger.debug('数据库操作异常..{}'.format(str(e)))
      return '数据库异常'
    return 'ok', [operation_mode_ret, ask_duration, datetime_duration]

  def change_datetime_duration(self, datetime_duration):
    try:
      myquery = {}
      newvalues = {"$set": {"datetime_duration": datetime_duration}}
      self.db.update_data(self.db.params_db, "device_info", myquery, newvalues)
      self.datetime_duration = datetime_duration
    except Exception as e:
      self.logger.debug('数据库操作异常..{}'.format(str(e)))
      return '数据库异常'
    return 'ok'

  def change_ask_duration(self, ask_duration):
    try:
      myquery = {}
      newvalues = {"$set": {"ask_duration": ask_duration}}
      self.db.update_data(self.db.params_db, "device_info", myquery, newvalues)
      self.ask_duration = ask_duration
    except Exception as e:
      self.logger.debug('数据库操作异常..{}'.format(str(e)))
      return '数据库异常'
    return 'ok'

  def change_operation_mode(self, operation_mode):
    if operation_mode == '手动模式':
      operation_mode_index = 0
    elif operation_mode == '半自动模式':
      operation_mode_index = 2
    else:
      operation_mode_index = 1
    try:
      data = self.db._get_data(self.db.params_db, "device_info")[1][0]
      old_operation_mode_index = data.get('operation_mode')
      if old_operation_mode_index != operation_mode_index:
        # 如果操作模式发生了变化
        settings.ORDER_OR_MODE_CHANGE_FLAG = True
      else:
        return 'ok'  # 如果操作模式没有变化,直接返回就可以了.
      myquery = {}
      newvalues = {"$set": {"ask_duration": operation_mode_index}}
      self.db.update_data(self.db.params_db, "device_info", myquery, newvalues)
      self.operation_mode = operation_mode_index
    except Exception as e:
      self.logger.debug('数据库操作异常..{}'.format(str(e)))
      return '数据库异常'
    return 'ok'

  def modify_buff_size(self, hd_sock):
    # 获取当前套接字关联的选项,socket.SOL_SOCKET,正在使用的socket,socket.SOCK_STREAM发送缓存区的大小
    bsize_send = hd_sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
    bsize_recv = hd_sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
    print('Buffer size [Before]:{},{}'.format(bsize_send, bsize_recv))

    # 设置TCP套接字关联的选项
    # socket.TCP_NODELAY TCP层套接口选项
    # socket.TCP_NODELAY.表示禁用了Nagle,允许小包发送.不用等很多的包折合一起再发送的问题.适用于数据量小的项目.
    hd_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)

    # 设置发送缓存区套接字关联的选项
    hd_sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, settings.BUFFER_SIZE)
    # 设置接收缓存区套接字关联的选项
    hd_sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, settings.BUFFER_SIZE)
    bsize_send = hd_sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
    bsize_recv = hd_sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
    print('Buffer size [After]:{},{}'.format(bsize_send, bsize_recv))

  def recv_msg(self):
    while True:
      if not self.sock:
        break
      recv_data = self.sock.recv(settings.BUFFER_SIZE)
      if recv_data:
        print(recv_data, type(recv_data))
        # if recv_data[:4] != b'\x02' or recv_data[-4:] != b'\x03':
        #     recv_msg = 'MESSAGE_ERROR'
        # else:
        recv_msg = recv_data
        msg_name, body_dict, ret = parse_recv_msg(recv_msg)
        print('in recv_msg,msg_name={},ret={}'.format(msg_name, ret))
        if ret == '0':
          self.deal_recv_msg(msg_name, body_dict)
        gevent.sleep(0.05)
      else:
        gevent.sleep(0.05)

  # 监听有没有收到恢复消息
  def server_reply_listen(self):
    start = time.time()
    # 如果间隔时间小于50s,就一直检测
    while time.time() - start < self.ask_duration - 10:
      if settings.EAP_REPLY:
        print('检测到了服务器的回复...')
        settings.EAP_ONLINE = True
        break
      gevent.sleep(0.05)
    else:
      print('没有检测到设备的回复,设备不在线')

  def send_total_count(self):
    settings.TOTAL_COUNT_FLAG = True
    # 创建一个定时任务
    self.hd_timer_total_count = threading.Timer(self.total_count_duration, self.send_total_count)
    self.hd_timer_total_count.start()

  def send_ask(self):
    settings.ASK_SEND_FLAG = True
    # 创建一个定时任务,每隔一段时间向服务器发送是否存在
    self.hd_timer_ask = threading.Timer(self.ask_duration, self.send_ask)
    self.hd_timer_ask.start()

  # 设备向EAP发送设备时间信息给EAP
  def send_date_time(self):
    print("in send_date_time")
    settings.DATETIME_SEND_FLAG = True
    # 创建一个定时任务,每隔一段时间向服务器发送时间校准
    self.hd_timer_datetime = threading.Timer(self.datetime_duration * 60, self.send_date_time)
    self.hd_timer_datetime.start()

  def manager(self):
    # 调用定时器,一直向服务器发送你是否存在的包.
    self.send_ask()
    while True:
      if settings.IS_MAIN_ALIVE:
        # 获取设备的状态
        data = self.db._get_data(self.db.params_db, "device_info")[1][0]
        new_status = data.get('equipment_status')
        if new_status != self.equipment_status:  # 设备状态改变
          settings.EQUIPMENT_STATUS_CHANGE = True
        try:
          if not self.recv_thread.is_alive():
            self.recv_thread = threading.Thread(target=self.recv_msg, name="recv_thread")
            self.recv_thread.setDaemon(True)
            self.recv_thread.start()

          if not self.send_thread.is_alive():
            self.send_thread = threading.Thread(target=self.send_msg, name='send_thread')
            self.send_thread.setDaemon(True)
            self.send_thread.start()
        except Exception as e:
          self.logger.debug('Manager Error:{}'.format(str(e)))
      else:
        self.hd_timer_ask.cancel()  # 主线程结束,取消定时任务.
        self.hd_timer_datetime.cancel()  # 主线程结束,取消定时任务.
        self.hd_timer_total_count.cancel()  # 主线程结束,取消定时任务.
        self.msg_send_type = 'disconnect'
        self.sock.close()
      gevent.sleep(0.05)

  def sync_sys_time(self, service_time):
    year = int(service_time[:4])
    month = int(service_time[4:6])
    day = int(service_time[6:8])
    hour = int(service_time[8:10])
    min = int(service_time[10:12])
    sec = int(service_time[12:])
    win32api.SetSystemTime(year, month, day, day, hour, min, sec, 0)

  def send_2_ui(self, msg):
    self.msg_q.put(json.dumps({"msg": msg}))

  def send_msg(self):
    while True:
      if not self.sock:  # 如果socket关闭,就退出
        break
      # 根据要发送的消息的类型
      if settings.INITIAL_DATA_FLAG:
        # EAP 向设备请求目前的基本状态
        head_msg = 'Initial Data Reply'
        body_dict_key = ["date_time", "operation_mode", "total_wip_count", "equipment_status", "recipe_name"]
        date_time = get_transaction_id()[:-3]  # 将后三位去除掉
        try:
          data = self.db._get_data(self.db.params_db, "device_info")[1][0]
          operation_mode = data.get('operation_mode')
          total_wip_count = data.get('total_wip_count')
          equipment_status = data.get('equipment_status')
          recipe_name = data.get('recipe_name')
          ret_dict = {"returncode": '0', "returnmessage": ''}
        except Exception as e:
          self.logger.debug('数据库异常!', str(e))
          ret_dict = {"returncode": '1', "returnmessage": '数据库异常,获取数据失败!'}
          data = {}
          operation_mode = data.get('operation_mode', 0)
          total_wip_count = data.get('total_wip_count', 0)
          equipment_status = data.get('equipment_status', 0)
          recipe_name = data.get('recipe_name')
          print('数据库异常!', str(e))
        # 将消息封装成字典..
        body_dict_value = [date_time, operation_mode, total_wip_count, equipment_status, recipe_name]
        body_dict = dict(zip(body_dict_key, body_dict_value))
        print('body_dict = {}'.format(body_dict))
        pack_send_message(head_msg, body_dict, ret_dict)
        msg_send = get_send_msg()
        self.sock.sendall(msg_send)
        settings.INITIAL_DATA_FLAG = False
      elif settings.ASK_SEND_FLAG:
        # 先构建消息XML文件
        pack_send_message(*settings.ASK_ARGS)
        with open(r'./ask_msg.xml', 'rb') as f:
          data = f.read()
        msg_send = bytes(chr(2),encoding='ascii')+ data + bytes(chr(3),encoding='ascii')
        self.sock.sendall(msg_send)
        settings.EAP_REPLY = False  # EAP回复的标志位先置为False
        settings.ASK_SEND_FLAG = False  # 消息发送完毕之后将标志位置为False
        # 开启一个执行50秒的函数,去执行一个监听函数,看看有么有接收到服务器的返回.
        t = threading.Thread(target=self.server_reply_listen)
        t.setDaemon(True)
        t.start()
      elif settings.DATETIME_SEND_FLAG:
        # 先构建消息XML文件
        msg_name = "Equipment Current Date Time"
        body_dict = {"date_time": get_transaction_id()[:-3]}
        ret_dict = {"returncode": "0", "returnmessage": ""}
        pack_send_message(msg_name, body_dict, ret_dict)
        msg_send = get_send_msg()
        self.sock.sendall(msg_send)
        settings.DATETIME_SEND_FLAG = False
      elif settings.DATETIME_SYMC_REPLY_FLAG:
        # 先构建要回复的XML消息
        msg_name = "Date Time Sync Reply"
        body_dict = {}
        ret_dict = {"returncode": "0", "returnmessage": ""}
        pack_send_message(msg_name, body_dict, ret_dict)
        msg_send = get_send_msg()
        self.sock.sendall(msg_send)
        settings.DATETIME_SYMC_REPLY_FLAG = False
      elif settings.ORDER_OR_MODE_CHANGE_FLAG:
        msg_name = "Equipment Information"
        data = self.db._get_data(self.db.params_db, "device_info")[1][0]
        recipe_name = data.get('recipe_name')
        body_dict = {"operation_mode": self.operation_mode, "recipe_name": recipe_name}
        ret_dict = {"returncode": "0", "returnmessage": ""}
        pack_send_message(msg_name, body_dict, ret_dict)
        msg_send = get_send_msg()
        self.sock.sendall(msg_send)
      elif settings.TOTAL_COUNT_FLAG:
        msg_name = "Equipment Total Count"
        data = self.db._get_data(self.db.params_db, "device_info")[1][0]
        total_wip_count = data.get("total_wip_count")
        body_dict = {"total_wip_count": total_wip_count}
        ret_dict = {"returncode": "0", "returnmessage": ""}
        pack_send_message(msg_name, body_dict, ret_dict)
        msg_send = get_send_msg()
        self.sock.sendall(msg_send)
      elif settings.EQUIPMENT_STATUS_CHANGE:
        msg_name = "Equipment Status"
        data = self.db._get_data(self.db.params_db, "device_info")[1][0]
        equipment_status = data.get("equipment_status")
        body_dict = {"equipment_status": equipment_status}
        ret_dict = {"returncode": "0", "returnmessage": ""}
        pack_send_message(msg_name, body_dict, ret_dict)
        msg_send = get_send_msg()
        self.sock.sendall(msg_send)
      elif settings.ALARM_REPORT_FLAG:
        msg_name = "Alarm Warning Data Report"
        data = self.db._get_data(self.db.params_db, "device_info")[1][0]
        alarm_type = data.get("alarm_type")
        report_type = data.get('report_type')
        aw_code = data.get('aw_code')
        aw_text = data.get('aw_text')
        body_dict = {"alarm_type": alarm_type, "report_type": report_type, "aw_code": aw_code, "aw_text": aw_text}
        ret_dict = {"returncode": "0", "returnmessage": ""}
        pack_send_message(msg_name, body_dict, ret_dict)
        msg_send = get_send_msg()
        self.sock.sendall(msg_send)
      elif settings.TRACE_DATA_REPLY_FLAG:
        msg_name = "Trace Data Reply"
        data = self.db._get_data(self.db.params_db, "device_info")[1][0]
        trace_list = data.get('trace_data')
        ret_dict = {"returncode": "0", "returnmessage": ""}
        pack_send_message(msg_name, trace_list, ret_dict)
        msg_send = get_send_msg()
        self.sock.sendall(msg_send)
      elif settings.RECIPE_EXSIT_REPLY:
        msg_send = get_send_msg()
        self.sock.sendall(msg_send)

      print('in send_msg')
      gevent.sleep(0.5)

  # 根据服务器发过来的消息,来对各种消息进行处理
  def deal_recv_msg(self, msg_type, body_dict):
    print("收到消息：msg_type = {}".format(msg_type))
    # 如果接收到了EAP设备的恢复,证明它是在线的
    if msg_type == 'Are You There Reply':
      settings.EAP_REPLY = True
    elif msg_type == 'Initial Data Request':
      settings.INITIAL_DATA_FLAG = True
    elif msg_type == 'Date Time Sync Command':
      settings.DATETIME_SYMC_REPLY_FLAG = True  # 将回复的Flag置为True,回复系统已经校准了时间.
      service_datetime = body_dict["date_time"]
      self.sync_sys_time(service_datetime)
    elif msg_type == 'Equipment Information Reply':  # EAP 收到设备消息,然后将标志位置位False
      settings.ORDER_OR_MODE_CHANGE_FLAG = False
    elif msg_type == 'Equipment Total Count Reply':
      settings.TOTAL_COUNT_FLAG = False
    elif msg_type == 'Equipment Status Reply':
      settings.EQUIPMENT_STATUS_CHANGE = False
    elif msg_type == 'Alarm Warning Data Reply':
      settings.ALARM_REPORT_FLAG = False
    elif msg_type == 'Trace Data Request':
      settings.TRACE_DATA_REPLY_FLAG = True
    elif msg_type == 'Recipe Body Modify Reply':
      settings.RECIPE_MODIFY_FLAG = False
    elif msg_type == 'Recipe Name Exist Request':
      recipe_name = body_dict['recipe_name']
      query = {"id": recipe_name}
      c = self.db._get_data(self.db.params_db, "order", query)[1]
      if len(list(c)) > 0:
        settings.RECIPE_EXSIT = True
        ret_dict = {"returncode": "0", "returnmessage": ""}
      else:
        settings.RECIPE_EXSIT = False
        ret_dict = {"returncode": "1", "returnmessage": "配方不存在"}
      msg_name = "Recipe Name Exist Reply"
      body_dict = {'recipe_name', recipe_name}
      pack_send_message(msg_name, body_dict, ret_dict)
      settings.RECIPE_EXSIT_REPLY = True
    elif msg_type == 'Recipe Body Request':
      recipe_name = body_dict['recipe_name']
      query = {"id": recipe_name}
      c = self.db._get_data(self.db.params_db, "order", query)[1]


if __name__ == '__main__':
  logger = log_init(__name__)
  db = DB_handler()
  msg_queue = queue.Queue()  # 消息队列
  client = Hd_Client(settings.SERVICE_IP, settings.PORT, logger, db, msg_queue)
  settings.IS_MAIN_ALIVE = True
  client.manager_thread.setDaemon(True)
  client.manager_thread.start()
  while True:
    settings.IS_MAIN_ALIVE = True
    print('主线程在执行...,is_main_alive = {}'.format(settings.IS_MAIN_ALIVE))
    gevent.sleep(1)
